#include "config.h"

#include "lib/libebc/ebc.h"

static ecm_uint32	scenario;

static ECMBOOL		child_mode;
static char		*progname;

static ecm_sockfd_t	sockfd;
static ecm_uint16	port = 19999;

static void
usage(void)
{
	printf(
"Usage: ecmsock_test <options> [<scenario #>]\n"
" Options:\n"
"   -h: this message\n"
"   -c: child mode\n"
" Scenario #:\n"
"  1: bind same port test\n"
"  2: accept timeout test\n"
"  3: transfer flags test\n"
	);
}

static void
parse_args(int argc, char *argv[])
{
	int	c;

	while ((c = ecm_getopt(argc, argv, "hc")) != -1) {
		switch(c) {
		case 'h':
			usage();
			exit(0);
			break;
		case 'c':
			child_mode = ECM_TRUE;
			break;
		default:
			usage();
			exit(1);
			break;
		}
	}

	if (argc > ecm_optind) {
		if (ecm_sscanf(argv[ecm_optind], "%u", &scenario) != 1) {
			usage();
			exit(1);
		}
	}
}

static void
exec_child(ecm_sockfd_t sockfd, ecm_uint32 scenario_no)
{
	ecm_exectx_t	ctx;
	ecm_err_t	err;
	ecm_sockaddr_t sockaddr;
	char *addr;
	ecm_uint16 port;
	char	*command;
	char	*envs[] = { NULL, NULL };
	char	*env;

	ecm_sock_getsockname(sockfd, sockaddr);
	ecm_sock_get_ipv4_sockaddr(sockaddr, &addr, &port);
	ecm_free(addr);

	ecm_init_exectx(&ctx);

	ecm_asprintf(&command, "%s -c %u", progname, scenario_no);
	ctx.command = command;
	ctx.env_arr = (const char **)envs;

	ecm_asprintf(&env, "PORT=%u", port);
	envs[0] = env;

	if ((err = ecm_exec_process(&ctx, NULL, NULL)) != ECM_OK)
		ecm_test_failed("exec failed: %d", err);

	ecm_clear_exectx(&ctx);
	ecm_free(command);
}

static void
accept_func(ecm_ptr_t arg)
{
	ecm_sockfd_t	clisockfd;
	ebc_err_t	err;
	ecm_uint32	timeout;

	ecm_test_start("accepting for 1 second");
	timeout = 1000;
	err = ecm_sock_accept(sockfd, &clisockfd, &timeout);
	if (err != -ERR_EBC_TIMEOUT)
		ecm_test_failed("not -ERR_EBC_TIMEOUT: %d\n", err);
	ecm_test_ok();

	ecm_test_start("cancel accept with infinite timeout");

	err = ecm_sock_accept(sockfd, &clisockfd, NULL);

	if (err != -ERR_EBC_CANCELLED)
		ecm_test_failed("accept error: %d", err);

	ecm_test_ok();
}

static void
killer_func(ecm_ptr_t arg)
{
	ecm_uint32	sleeptime = (ecm_uint32)(ecm_iptr_t)arg;

	ecm_sleep(sleeptime);
	ecm_test_failed("something wrong");
}

static void
bind_another(void)
{
	ecm_sockfd_t	sockfd;
	ecm_sockaddr_t	addr;
	ebc_err_t	err;

	ecm_test_start("bind same port");
	if ((sockfd = ecm_sock_socket(ECM_SOCK_TYPE_TCP)) == NULL)
		ecm_test_failed("cannot create socket");

	if (!ecm_sock_get_sockaddr_ipv4(NULL, port, addr))
		ecm_test_failed("cannot get socket address");

	err = ecm_sock_bind(sockfd, addr);
	if (err != -ERR_EBC_EXIST)
		ecm_test_failed("bind additional: %d", err);

	ecm_test_ok();
}

static void
safe_bind(ecm_sockfd_t sockfd)
{
	ecm_sockaddr_t	addr;
	ebc_err_t	err;

again:
	if (!ecm_sock_get_sockaddr_ipv4(NULL, port, addr))
		ecm_test_failed("ecm_sock_get_sockaddr_ipv4");

	err = ecm_sock_bind(sockfd, addr);
	if (err == EBC_OK)
		return;
	if (err == -ERR_EBC_EXIST) {
		port++;
		goto again;
	}
	ecm_test_failed("cannot bind: %d", err);
}

static void
run_scenario_1(void)
{
	ecm_sockfd_t	sockfd;

	if ((sockfd = ecm_sock_socket(ECM_SOCK_TYPE_TCP)) == NULL)
		ecm_test_failed("cannot create socket");

	safe_bind(sockfd);

	bind_another();
	ecm_sock_close(sockfd);
}

static void
run_scenario_2(void)
{
	ecm_thread_t	accepter;
	ecm_thread_t	killer;

	if ((sockfd = ecm_sock_socket(ECM_SOCK_TYPE_TCP)) == NULL)
		ecm_test_failed("cannot create socket");

	if (ecm_sock_listen(sockfd, 5) != EBC_OK)
		ecm_test_failed("cannot linsten");

	ecm_test_verbose("start accepting thread\n");

	ecm_thread_create(&accepter, accept_func, NULL);

	ecm_test_verbose("wait for 3 seconds\n");
	ecm_sleep(3000);

	ecm_test_verbose("try to cancel accept thread\n");
	ecm_sock_cancel(sockfd);

	ecm_thread_create(&killer, killer_func, (ecm_ptr_t)1000);

	ecm_thread_free(accepter);
	ecm_thread_terminate(killer);

	ecm_sock_close(sockfd);
}

static void
cancel_func(ecm_ptr_t arg)
{
	ecm_sockfd_t	sockfd = (ecm_sockfd_t)arg;

	ecm_sleep(1000);
	ecm_sock_cancel(sockfd);
}

static void
run_scenario_3(void)
{
	ecm_sockfd_t	sockfd_accept, sockfd_client;
	ecm_thread_t	killer;
	ecm_uint32	timeout = 3000;
	char		buf[1024];
	ecm_size_t	buflen;
	ebc_err_t	err;

	ecm_test_start("ecm sock transfer flags test");

	if ((sockfd_accept = ecm_sock_socket(ECM_SOCK_TYPE_TCP)) == NULL)
		ecm_test_failed("cannot create socket");

	if (ecm_sock_listen(sockfd_accept, 5) != EBC_OK)
		ecm_test_failed("cannot linsten");

	exec_child(sockfd_accept, 3);

	err = ecm_sock_accept(sockfd_accept, &sockfd_client, &timeout);
	if (err != EBC_OK)
		ecm_test_failed("cannot accept: %d\n", err);

	ecm_test_verbose("trying to recv 128 bytes with ECM_SOCK_TRANSFLAG_PEEK option\n");
	buflen = 128;
	err = ecm_sock_recv(sockfd_client, buf, &buflen, ECM_SOCK_TRANSFLAG_PEEK, NULL);
	if (err != EBC_OK)
		ecm_test_failed("cannot recv: %d\n", err);
	if (buflen != 128)
		ecm_test_failed("peek data is not 128");

	ecm_test_verbose("trying to recv 1024 bytes with ECM_SOCK_TRANSFLAG_WAITALL option\n");
	buflen = 1024;
	timeout = 1000;
	err = ecm_sock_recv(sockfd_client, buf, &buflen, ECM_SOCK_TRANSFLAG_WAITALL, &timeout);
	if (err != -ERR_EBC_TIMEOUT)
		ecm_test_failed("cannot recv: %d\n", err);
	if (buflen != 256)
		ecm_test_failed("wait all data is not 256");

	buflen = 1024;
	ecm_test_verbose("trying to recv 1024 bytes with ECM_SOCK_TRANSFLAG_WAITALL option\n");
	err = ecm_sock_recv(sockfd_client, buf, &buflen, ECM_SOCK_TRANSFLAG_WAITALL, NULL);
	if (err != EBC_OK)
		ecm_test_failed("cannot recv while receiving data all: %d\n", err);
	if (buflen != 1024)
		ecm_test_failed("wait all data is not 1024");

	ecm_test_ok();

	ecm_test_start("cancel ecm socket recv");

	buflen = 1024;

	ecm_thread_create(NULL, cancel_func, sockfd_client);
	ecm_thread_create(&killer, killer_func, (ecm_ptr_t)3000);

	err = ecm_sock_recv(sockfd_client, buf, &buflen, ECM_SOCK_TRANSFLAG_WAITALL, NULL);
	if (err != -ERR_EBC_CANCELLED)
		ecm_test_failed("ecm_sock_recv could not be cancelled: %d\n", err);

	ecm_test_ok();

	ecm_thread_terminate(killer);
	ecm_sock_close(sockfd_accept);
	ecm_sock_close(sockfd_client);
}

static void
run_scenario_3_child(void)
{
	ecm_sockfd_t	sockfd;
	ecm_sockaddr_t	addr;
	ecm_uint16	port;
	char	buf[1024];
	ecm_size_t	buflen;

	if (ecm_sscanf(ecm_getenv("PORT"), "%hu", &port) != 1)
		return;
	if (!ecm_sock_get_sockaddr_ipv4(NULL, port, addr))
		return;
	if ((sockfd = ecm_sock_socket(ECM_SOCK_TYPE_TCP)) == NULL)
		return;
	ecm_sleep(1000);
	if (ecm_sock_connect(sockfd, addr, 3000) != EBC_OK)
		return;
	buflen = 256;
	ecm_sock_send(sockfd, buf, &buflen, 0, ECM_TRUE);
	ecm_sleep(2000);
	buflen = 1024;
	ecm_sock_send(sockfd, buf, &buflen, 0, ECM_TRUE);

	buflen = 1;
	ecm_sock_recv(sockfd, buf, &buflen, 0, NULL);
}

static void
run_all_scenarios(void)
{
	run_scenario_1();
	run_scenario_2();
	run_scenario_3();
}

int
main(int argc, char *argv[])
{
	ebc_initialize();

	progname = ecm_get_progname();
	parse_args(argc, argv);

	if (!child_mode) {
		switch (scenario) {
		case 0:
			run_all_scenarios();
			break;
		case 1:
			run_scenario_1();
			break;
		case 2:
			run_scenario_2();
			break;
		case 3:
			run_scenario_3();
			break;
		default:
			ecm_fprintf(ecm_stderr, "unknown scenario: %u", scenario);
			break;
		}
	}
	else {
		switch (scenario) {
		case 3:
			run_scenario_3_child();
			break;
		default:
			ecm_fprintf(ecm_stderr, "unknown scenario(child): %u", scenario);
			break;
		}
	}

	return 0;
}

